package com.itheima.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.domain.dto.RedisData;
import com.itheima.domain.entity.Shop;
import com.itheima.domain.vo.Result;
import com.itheima.mapper.ShopMapper;
import com.itheima.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.jsonwebtoken.lang.Collections;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 坤哥
 * @since 2025-10-06
 */
@Service
@Slf4j
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    // 自定义线程池（用于异步更新缓存）
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);


    /**
     * 把热门店铺存入到redis（逻辑过期）
     */
    @Override
    public Result saveShopToRedisLogic(Long shopId,Long expireTime) {
        //从数据库查询店铺的详细信息
        Shop shop = getById(shopId);
        //把信息封装进redisData实体类
        RedisData<Shop> shopRedisData = new RedisData<>();
        shopRedisData.setData(shop);
        shopRedisData.setExpireTime(LocalDateTime.now().plusSeconds(expireTime));
        //把redisData保存进redis
        stringRedisTemplate.opsForValue().set("cache:shop:" + shopId,JSONUtil.toJsonStr(shopRedisData));
        return Result.ok();
    }
    /**
     * 根据店铺id查询店铺信息  /  并发访问热点店铺
     */
    @Override
    public Result queryShopById(Long shopId) {
        //从redis中查询数据
        String redisData = stringRedisTemplate.opsForValue().get("cache:shop:" + shopId);
        if(StrUtil.isBlank(redisData)){
            return Result.fail("店铺不存在");
        }
        //查看是否逻辑过期
        RedisData<Shop> shopRedisData = JSONUtil.toBean(redisData, new TypeReference<RedisData<Shop>>() {}, false);//false --是否 忽略字段匹配错误
        LocalDateTime expireTime = shopRedisData.getExpireTime();
        if(expireTime.isAfter(LocalDateTime.now())){//未过期
            return Result.ok(shopRedisData.getData());
        }
        //过期，获取互斥锁，得到锁的线程去更新redis，然后返回旧的数据
        RLock lock = redissonClient.getLock("lock:cache:shop:" + shopId);//给锁设置一个唯一标识
        boolean success = false;
        try {
            success = lock.tryLock();//所有的线程都去尝试获取锁，只有获取到锁的线程才有资格去更新redis数据
            if(success){//获取到锁
                CompletableFuture.runAsync(()->{
                    Shop shop = getById(shopId);
                    RedisData<Shop> shopRedisData1 = new RedisData<>();
                    shopRedisData1.setData(shop);
                    shopRedisData1.setExpireTime(LocalDateTime.now().plusHours(1L));
                    stringRedisTemplate.opsForValue().set("cache:shop:" + shopId,JSONUtil.toJsonStr(shopRedisData1));
                    log.info("已把对应的店铺数据更新到redis");
                },CACHE_REBUILD_EXECUTOR);
            }
            return Result.ok(shopRedisData.getData());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            if(success){
                lock.unlock();//获取到锁才能释放锁
            }
        }
    }
    /**
     * 发布笔记时模糊分页查询店铺列表
     */
    @Override
    public Result queryShopList(String name, Integer current) {
        Page<Shop> page = new Page<>(current,10);
        Page<Shop> iPage = lambdaQuery().like(StrUtil.isNotBlank(name), Shop::getName, name).page(page);//如果name不为空才模糊查询，不然就是查询全部
        List<Shop> shops = iPage.getRecords();
        if(Collections.isEmpty(shops)){
            return Result.ok();
        }
        return Result.ok(shops);
    }
}
